package com.mon4cc.simulation;

import com.mon4cc.simulation.logWriter.LogWriter;
import com.mon4cc.simulation.utils.IdGenerator;
import org.jbpt.pm.ControlFlow;
import org.jbpt.pm.DataNode;
import org.jbpt.pm.FlowNode;
import org.jbpt.pm.IDataNode;
import org.jbpt.pm.bpmn.Bpmn;
import org.jbpt.pm.bpmn.BpmnControlFlow;
import org.jbpt.pm.bpmn.EndEvent;
import org.jbpt.pm.bpmn.StartEvent;
import org.springframework.util.ObjectUtils;

import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author Ping
 * @date 2022/12/19 11:31
 **/
public class TokenBasedBPMNSimulation implements BPMNExecution  {
    private Bpmn<BpmnControlFlow<FlowNode>, FlowNode> bpmn ;
    private String executeState ;
    private SimulationConfig sconfig ;
    private StartEvent startEvent;
    private EndEvent endEvent;
    private IdGenerator caseIdGenerator ;
    private LogWriter writer ;



    public TokenBasedBPMNSimulation(Bpmn<BpmnControlFlow<FlowNode>, FlowNode> bpmn, StartEvent startEvent,
                                              EndEvent endEvent, LogWriter writer , SimulationConfig sconfig) {
        this.bpmn = bpmn;
        this.sconfig = sconfig;
        this.startEvent = startEvent ;
        this.endEvent = endEvent ;
        this.executeState = BPMNExecution.INACTIVATED ;
        this.writer = writer ;
        caseIdGenerator = new IdGenerator(System.currentTimeMillis()) ;
    }


    public SimulationConfig getSimulationConfig() {
        return sconfig;
    }

    public void setSimulationConfig(SimulationConfig vconfig) {
        this.sconfig = vconfig;
    }

    @Override
    public StartEvent getStartEvent() {
        return startEvent;
    }
    @Override
    public EndEvent getEndEvent() {
        return endEvent;
    }

    @Override
    public Bpmn<BpmnControlFlow<FlowNode>, FlowNode> getBpmn() {
        return this.bpmn;
    }

    @Override
    public void execute(ExecuteContext context) {
        FlowNodeSimulatedInstance startInstance = new StartEventSimulatedInstance(this) ;
        context.setCaseId(this.caseIdGenerator.nextId());
        this.setState(BPMNExecution.RUNING);
        startInstance.doExecute(context);
    }

    @Override
    public void multiExecute(ExecuteContext context, int executeTime) {

    }

    @Override
    public void setState(String state) {
        this.executeState = state ;
    }

    @Override
    public boolean finished() {
        return this.executeState == BPMNExecution.FINISHED ;
    }

    @Override
    public void stopExecution(ExecuteContext context) {
        setState(BPMNExecution.FINISHED);
    }

    @Override
    public Map<String, Object> getSimulatedValueForDataObject(FlowNode flowNode) {
        Set<IDataNode> dataNodes = (Set<IDataNode>) flowNode.getWriteDocuments() ;
        if (ObjectUtils.isEmpty(dataNodes)) {
            return null ;
        }
        Set<String> dataVarNames = dataNodes.stream().map(IDataNode::getName).collect(Collectors.toSet());
        return this.sconfig.simulatedDataFromDataObject(dataVarNames);
    }

    @Override
    public Set<BpmnControlFlow<FlowNode>> getOutputControlflow(FlowNode flowNode) {
        return transformToSet(this.bpmn.getOutgoingControlFlow(flowNode))  ;
    }

    public LogWriter getWriter() {
        return writer;
    }

    @Override
    public Set<BpmnControlFlow<FlowNode>> getInputControlflow(FlowNode flowNode) {
        return transformToSet(this.bpmn.getIncomingControlFlow(flowNode)) ;
    }
    private Set<BpmnControlFlow<FlowNode>> transformToSet(Collection<ControlFlow<FlowNode>> controlFlows) {
        if (ObjectUtils.isEmpty(controlFlows)) {
            return null ;
        }
        Set<BpmnControlFlow<FlowNode>> controlFlowSet = new HashSet<>() ;
        controlFlows.forEach(cf -> {
            controlFlowSet.add((BpmnControlFlow<FlowNode>)cf) ;
        }) ;
        return controlFlowSet ;
    }

    @Override
    public boolean containedInInvalidatedDataAssignementEvents(String instanceName) {
        return this.sconfig.containedInInvalidatedDataAssignementEvents(instanceName);
    }
}
